package com.zxj.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.github.pagehelper.PageHelper;
import com.zxj.common.core.domain.vo.PageVO;
import com.zxj.common.core.enums.ExamStatusEnum;
import com.zxj.common.core.enums.ResultCode;
import com.zxj.common.security.exception.ServiceException;
import com.zxj.system.domain.exam.dto.ExamAddDTO;
import com.zxj.system.domain.exam.dto.ExamEditDTO;
import com.zxj.system.domain.exam.dto.ExamQueryPageDTO;
import com.zxj.system.domain.exam.dto.ExamQuestAddDTO;
import com.zxj.system.domain.exam.entity.Exam;
import com.zxj.system.domain.exam.entity.ExamQuestion;
import com.zxj.system.domain.exam.vo.ExamAddVO;
import com.zxj.system.domain.exam.vo.ExamDetailVO;
import com.zxj.system.domain.exam.vo.ExamVO;
import com.zxj.system.domain.question.entity.Question;
import com.zxj.system.domain.question.vo.QuestionVO;
import com.zxj.system.manager.ExamCacheManager;
import com.zxj.system.mapper.exam.ExamMapper;
import com.zxj.system.service.exam.IExamService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 竞赛表结构 服务实现类
 * </p>
 *
 * @author zxj2022
 * @since 2024-12-29
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements IExamService {
    private final ExamCacheManager examCacheManager;


    /**
     * 分页查询竞赛列表
     * @param examQueryPageDTO
     * @return
     */
    @Override
    public PageVO<ExamVO> pageQuery(ExamQueryPageDTO examQueryPageDTO) {
        PageHelper.startPage(examQueryPageDTO.getPageNo(), examQueryPageDTO.getPageSize());
        List<ExamVO> examVOList = this.baseMapper.selectExamList(examQueryPageDTO);
        return PageVO.of(examVOList);
    }

    /**
     * 添加竞赛基本信息
     * @param examAddDTO 竞赛基本信息
     * @return 竞赛id
     */
    @Override
    public ExamAddVO add(ExamAddDTO examAddDTO) {
        // 1. 竞赛基本信息校验
        this.checkExamSaveParams(examAddDTO, null);

        // 2. 添加竞赛基本信息
        Exam exam = BeanUtil.copyProperties(examAddDTO, Exam.class);
        exam.setStatus(ExamStatusEnum.NOT_PUBLISH.getValue());
        if (!this.save(exam)) {
            log.error("添加竞赛基本信息失败, examAddDTO:{}", examAddDTO);
            throw new ServiceException(ResultCode.FAILED);
        }
        return ExamAddVO.builder().examId(exam.getExamId().toString()).build();
    }

    /**
     * 添加竞赛题目
     * @param examQuestAddDTO
     * @return
     */
    @Override
    public boolean questionAdd(ExamQuestAddDTO examQuestAddDTO) {
        // 1. 验证竞赛是否存在
        getExamById(examQuestAddDTO.getExamId());

        // 2. 验证题目是否存在
        // 构造查询语句: select count(*) from tb_question where question_id in (#{examQuestAddDTO.getQuestionIdSet()});
        long count = Db.lambdaQuery(Question.class)
                .in(Question::getQuestionId, examQuestAddDTO.getQuestionIdSet())
                .count();
        if (count != examQuestAddDTO.getQuestionIdSet().size()) {
            log.error("为竞赛新增的题目不存在, examId:{}, questionIdSet:{}", examQuestAddDTO.getExamId(), examQuestAddDTO.getQuestionIdSet());
            throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }

        // 3. 添加竞赛题目
        // 3.1 构建ExamQuestion对象
        List<ExamQuestion> examQuestionList = new ArrayList<>();
        int orderCount = 1;
        for (Long questionId : examQuestAddDTO.getQuestionIdSet()) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(examQuestAddDTO.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(orderCount++);
            examQuestionList.add(examQuestion);
        }

        // 3.2 批量插入
        return Db.saveBatch(examQuestionList);
    }

    /**
     * 删除竞赛题目
     * @param examId 竞赛id
     * @param questionId 题目id
     * @return
     */
    @Override
    public boolean questionDelete(Long examId, Long questionId) {
        return Db.lambdaUpdate(ExamQuestion.class)
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId)
                .remove();
    }

    /**
     * 依据ID获取竞赛详情
     * @param examId 竞赛id
     * @return 竞赛详情
     */
    @Override
    public ExamDetailVO detail(Long examId) {
        // 1. 获得竞赛基本信息
        Exam exam = getExamById(examId);

        // 2. 获得竞赛题目信息
        // 查询语句: select * from tb_exam_question where exam_id = #{examId};
        // 2.1 查询题目id
        List<ExamQuestion> list = Db.lambdaQuery(ExamQuestion.class)
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId)
                .list();
        // 2.2 查询题目信息
        List<Question> questions = null;
        if (CollectionUtil.isNotEmpty(list)) {
            List<Long> ids = list.stream().map(ExamQuestion::getQuestionId).toList();
            questions = Db.lambdaQuery(Question.class)
                    .in(Question::getQuestionId, ids)
                    .list();
        }


        // 3. 构建返回对象
        ExamDetailVO examDetailVO = BeanUtil.copyProperties(exam, ExamDetailVO.class);
        examDetailVO.setExamQuestionList(BeanUtil.copyToList(questions, QuestionVO.class));

        return examDetailVO;
    }

    private Exam getExamById(Long examId) {
        Exam exam = this.getById(examId);
        if (exam == null) {
            log.error("竞赛不存在, examId:{}", examId);
            throw new ServiceException(ResultCode.EXAM_NOT_EXISTS);
        }
        return exam;
    }

    /**
     * 编辑竞赛基本信息
     * @param examEditDTO
     * @return
     */
    @Override
    public int edit(ExamEditDTO examEditDTO) {
        // 1. 验证竞赛基本信息是否存在
        this.checkExamSaveParams(examEditDTO,examEditDTO.getExamId());

        // 2. 编辑竞赛基本信息
        Exam exam = BeanUtil.copyProperties(examEditDTO, Exam.class);
        return this.baseMapper.updateById(exam);
    }

    private void checkExamSaveParams(ExamAddDTO examAddDTO, Long examId) {
        //1、竞赛标题是否重复进行判断   2、竞赛开始、结束时间进行判断
        List<Exam> examList = this.baseMapper
                .selectList(new LambdaQueryWrapper<Exam>()
                        .eq(Exam::getTitle, examAddDTO.getTitle())
                        .ne(examId != null, Exam::getExamId, examId));
        if (CollectionUtil.isNotEmpty(examList)) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }
        if (examAddDTO.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);  //竞赛开始时间不能早于当前时间
        }
        if (examAddDTO.getStartTime().isAfter(examAddDTO.getEndTime())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
        }
    }

    /**
     * 删除竞赛
     * @param examId 竞赛id
     * @return
     */
    @Override
    @Transactional
    public int delete(Long examId) {
        // 1. 删除前竞赛状态判断
        Exam exam = getExamById(examId);
        if (exam.getStatus().equals(ExamStatusEnum.PUBLISHED.getValue())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        if (exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }

        // 2. 删除竞赛和题目关联关系
        Db.lambdaUpdate(ExamQuestion.class)
                .eq(ExamQuestion::getExamId, examId)
                .remove();

        // 3. 删除竞赛基本信息
        return this.baseMapper.deleteById(examId);
    }

    /**
     * 发布竞赛
     * @param examId 竞赛id
     * @return
     */
    @Override
    public int publish(Long examId) {
        // 1. 验证竞赛是否可以操作
        Exam exam = getExamById(examId);
        verifyExamTime(exam);

        if (exam.getStatus().equals(ExamStatusEnum.PUBLISHED.getValue())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        // 2. 验证竞赛是否有题目
        // 查询语句: select count(0) from tb_exam_question where exam_id = #{examId};
        Long count = Db.lambdaQuery(ExamQuestion.class)
                .eq(ExamQuestion::getExamId, examId)
                .count();
        if (count == null || count <= 0) {
            throw new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
        }

        // 3. 发布竞赛
        exam.setStatus(ExamStatusEnum.PUBLISHED.getValue());

        // 4. 将新发布的竞赛数据存储到redis
        examCacheManager.addCache(exam);

        return this.baseMapper.updateById(exam);
    }

    private static void verifyExamTime(Exam exam) {
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }

        if (exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }
    }

    /**
     * 取消发布竞赛
     * @param examId 竞赛id
     * @return
     */
    @Override
    public int cancelPublish(Long examId) {
        // 1. 验证竞赛是否可以操作
        Exam exam = getExamById(examId);

        verifyExamTime(exam);

        if (exam.getStatus().equals(ExamStatusEnum.NOT_PUBLISH.getValue())) {
            throw new ServiceException(ResultCode.EXAM_NOT_PUBLISH);
        }
        // 2. 取消发布竞赛
        exam.setStatus(ExamStatusEnum.NOT_PUBLISH.getValue());

        // 3. 删除缓存
        examCacheManager.deleteCache(examId);
        return this.baseMapper.updateById(exam);
    }
}
